/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2019 HUST Dian Group
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Original Author: Pengyu Liu <eicliupengyu@gmail.com>
 */
#include "ns3/integer.h"
#include "ns3/double.h"
#include "ns3/enum.h"
#include "ns3/pointer.h"
#include "ns3/log.h"
#include "ns3/string.h"
#include "robo-base.h"
#include "robo-ammo.h"
#include "robo-common.h"
#include "robo-judge.h"

namespace ns3 {
NS_LOG_COMPONENT_DEFINE ("RoboBase");
NS_OBJECT_ENSURE_REGISTERED (RoboBase);
uint8_t RoboBase::m_uidNum = 0;
RoboBase::RoboBase ()
{
  NS_LOG_FUNCTION (this);
  m_uid = m_uidNum++;
  for (auto &each : m_weapon)
    {
      each.m_heat = 0;
      each.m_nextShootTime = Seconds (0);
    }
  AddCollisionCallback (MakeCallback (&RoboBase::HandleCollision, this));
  m_initPlot = MakeCallback (&RoboBase::InitPlot, this);
  m_updatePlot = MakeCallback (&RoboBase::PlotSelf, this);
  m_heatTimer = Timer (Timer::CANCEL_ON_DESTROY);
  m_heatTimer.SetFunction (&RoboBase::HandleHeat, this);
  m_heatTimer.SetDelay (Seconds (0.1));
  m_heatTimer.Schedule ();
  m_expIncTimer = Timer (Timer::CANCEL_ON_DESTROY);
  m_expIncTimer.SetFunction (&RoboBase::HandleExpAutoInc, this);
  m_expIncTimer.SetDelay (Seconds (12));
  m_expIncTimer.Schedule ();
  m_virtualShieldTimer = Timer (Timer::CANCEL_ON_DESTROY);
  m_virtualShieldTimer.SetFunction (&RoboBase::HandleVirtualShield, this);
  m_virtualShieldTimer.SetDelay (Seconds (10));
  m_virtualShieldTimer.Schedule ();
}

RoboBase::~RoboBase ()
{
  NS_LOG_FUNCTION (this);
}

TypeId
RoboBase::GetTypeId (void)
{
  static TypeId tid =
      TypeId ("ns3::RoboBase")
          .SetParent<RoboActor> ()
          .SetGroupName ("Robo")
          .AddConstructor<RoboBase> ()
          .AddAttribute ("Jitter", "Shoot Jitter",
                         StringValue ("ns3::UniformRandomVariable[Min=-0.25|Max=0.25]"),
                         MakePointerAccessor (&RoboBase::m_random),
                         MakePointerChecker<RandomVariableStream> ())
          .AddAttribute ("CollisionInterval", "Collision Interval", TimeValue (Seconds (0.5)),
                         MakeTimeAccessor (&RoboBase::m_roboCollisionInterval),
                         MakeTimeChecker (Seconds (0.)))

      ;
  return tid;
}

TypeId
RoboBase::GetInstanceTypeId (void) const
{
  return GetTypeId ();
}

uint8_t
RoboBase::GetUid (void) const
{
  return m_uid;
}

uint8_t
RoboBase::GetTeam (void) const
{
  return m_team;
}

RoboType
RoboBase::GetRoboType (void) const
{
  return m_roboType;
}

std::string
RoboBase::GetName (void) const
{
  return m_name;
}

int
RoboBase::GetLife (void) const
{
  return int (m_life + 0.499);
}

int
RoboBase::GetLevel (void) const
{
  return m_level;
}

int
RoboBase::GetExp (void) const
{
  return m_exp;
}

int
RoboBase::GetHeat (AmmoType ammoType) const
{
  return m_weapon[ammoType].m_heat;
}

int
RoboBase::GetMaxHeat (AmmoType ammoType) const
{
  return gWeaponMaxHeat[m_roboType][ammoType][m_level];
}

bool
RoboBase::ShootAmmo (FVector speed, AmmoType ammoType)
{
  if (m_isDestroy || gRoboNotShootable[m_roboType][ammoType])
    {
      NS_LOG_DEBUG ((int) m_uid << " can\'t shoot");
      return false;
    }
  if (m_weapon[ammoType].m_ammoNumber < 1)
    {
      NS_LOG_DEBUG ((int) m_uid << " don\'t has ammo");
      return false;
    }
  if (Simulator::Now () < m_weapon[ammoType].m_nextShootTime)
    {
      NS_LOG_DEBUG ((int) m_uid << " shoot interval");
      return false;
    }
  float fspeed = speed.GetLength ();
  //射击超速扣血
  float overspeedLife =
      GetOverspeedLife (ammoType, fspeed, gWeaponMaxSpeed[ammoType][m_maxAmmoSpeedLevel],
                        gRoboMaxLife[m_roboType][m_maxLifeLevel]);
  if (!m_invincible)
    {
      m_life -= overspeedLife;
    }

  m_weapon[ammoType].m_ammoNumber--;
  m_weapon[ammoType].m_nextShootTime = Simulator::Now () + gWeaponShootDelta[ammoType];
  m_weapon[ammoType].m_heat += GetHeatOf (ammoType, fspeed);
  //过热扣血
  auto maxHeat = gWeaponMaxHeat[m_roboType][ammoType][m_level];
  if (m_weapon[ammoType].m_heat > (maxHeat << 1))
    {
      if (!m_invincible)
        {
          m_life -= 1.0 * (m_weapon[ammoType].m_heat - maxHeat) / 250 *
                    gRoboMaxLife[m_roboType][m_maxLifeLevel];
        }
      m_weapon[ammoType].m_heat = (maxHeat << 1);
    }
  Ptr<RoboAmmo> ammo = CreateObject<RoboAmmo> ();
  ammo->SetInitValue (m_globalLocation, gWeaponRedius[ammoType], gWeaponInjury[ammoType],
                      gWeaponRange[ammoType] / fspeed);
  ammo->m_shooterUid = m_uid;
  //热量高时射击准确度下降
  float factor = (m_weapon[ammoType].m_heat > maxHeat) + 1.f;
  speed += FVector (m_random->GetValue () * factor, m_random->GetValue () * factor);
  Ptr<VariableSpeedMobility> ammoMobility = CreateObject<VariableSpeedMobility> ();
  ammoMobility->SetActor (ammo);
  ammoMobility->SetSpeed (speed);
  ammo->SetMobility (ammoMobility);
  RoboJudge::Get ()->AddAmmo (ammo);
  if (m_life <= 0. && !m_isDestroy)
    {
      RoboJudge::Get ()->IndicateKillSelf (m_uid);
    }
  UpdateStatus ();
  return true;
}

bool
RoboBase::GiveAmmoTo (uint8_t targetId, int num, AmmoType ammoType)
{
  return RoboJudge::Get ()->GiveAmmoTo (m_uid, targetId, num, ammoType);
}

int
RoboBase::GetAmmoNum (AmmoType ammoType) const
{
  return m_weapon[ammoType].m_ammoNumber;
}

bool
RoboBase::UpgradeLifeLevel ()
{
  if (m_isDestroy || m_level * 2 + 2 <= m_maxAmmoSpeedLevel + m_maxLifeLevel ||
      m_maxLifeLevel >= MAX_PERFORMANCE_LEVEL - 1)
    return false;
  //升级时血量提升
  m_life += gRoboMaxLife[m_roboType][m_maxLifeLevel + 1] - gRoboMaxLife[m_roboType][m_maxLifeLevel];
  ++m_maxLifeLevel;
  RoboJudge::Get ()->IndicateLifeUpgrade (m_uid);
  return true;
}

bool
RoboBase::UpgradeMaxAmmoSpeed ()
{
  if (m_isDestroy || m_level * 2 + 2 <= m_maxAmmoSpeedLevel + m_maxLifeLevel ||
      m_maxAmmoSpeedLevel >= MAX_PERFORMANCE_LEVEL - 1)
    return false;
  ++m_maxAmmoSpeedLevel;
  RoboJudge::Get ()->IndicateMaxAmmoSpeedUpgrade (m_uid);
  return true;
}

void
RoboBase::HandleHeat ()
{
  NS_LOG_FUNCTION (this);
  for (int i = 0; i < MAX_WEAPON_ID; ++i)
    {
      auto maxHeat = gWeaponMaxHeat[m_roboType][i][m_level];
      if (m_weapon[i].m_heat > maxHeat && !m_invincible)
        {
          m_life -= 1.0 * (m_weapon[i].m_heat - maxHeat) / 2500 *
                    gRoboMaxLife[m_roboType][m_maxLifeLevel];
        }
      m_weapon[i].m_heat -= gWeaponCoolingValue[m_roboType][i][m_level];
      if (m_weapon[i].m_heat < 0)
        m_weapon[i].m_heat = 0;
    }
  if (m_life <= 0. && !m_isDestroy)
    {
      RoboJudge::Get ()->IndicateKillSelf (m_uid);
    }
  UpdateStatus ();
  m_heatTimer.Schedule ();
}

void
RoboBase::HandleExpAutoInc ()
{
  NS_LOG_FUNCTION (this);
  if (!m_isDestroy)
    m_exp += gRoboAutoIncExp[m_roboType];
  m_expIncTimer.Schedule ();
}

void
RoboBase::HandleVirtualShield ()
{
  NS_LOG_FUNCTION (this);
  m_virtualShield = gRoboVirtualShield[m_roboType];
  m_virtualShieldTimer.Schedule ();
}

void
RoboBase::UpdateStatus ()
{
  if (m_life <= 0)
    {
      m_life = 0;
      m_isDestroy = true;
    }
  for (; m_level + 1 < MAX_LEVEL && gRoboUpgradeExp[m_roboType][m_level + 1] <= m_exp; ++m_level)
    RoboJudge::Get ()->IndicateRoboUpgrade (m_uid);
}

void
RoboBase::HandleCollision (Ptr<RoboActor> oth)
{
  auto robo = DynamicCast<RoboBase> (oth);
  if (robo)
    {
      Ptr<VariableSpeedMobility> mob = DynamicCast<VariableSpeedMobility> (m_mobility);
      if (mob)
        {
          mob->RollBackLocation ();
          auto diff = (m_globalLocation - robo->m_globalLocation).Norm ();
          diff += FVector (diff.m_y, -diff.m_x);
          m_globalLocation += diff * RoboJudge::Get ()->m_updatePeroid;
        }
      if (robo->m_team == m_team)
        {
          return;
        }
      if (Simulator::Now () - m_lastRoboCollision > m_roboCollisionInterval)
        {
          RoboJudge::Get ()->IndicateShoot (m_uid, robo->m_uid, 2);
          m_lastRoboCollision = Simulator::Now ();
        }
    }
  else if (oth->m_collisionType == Collision_Type_Boundary)
    {
      Ptr<VariableSpeedMobility> mob = DynamicCast<VariableSpeedMobility> (m_mobility);
      if (mob)
        {
          mob->RollBackLocation ();
        }
    }
}

uint32_t
RoboBase::AddIndicateLocationCallback (IndicateLocationCallback cb)
{
  NS_LOG_FUNCTION (this);
  m_locationCallbackList.push_back (cb);
  return m_locationCallbackList.size ();
}

void
RoboBase::IndicateLocation (Ptr<RoboBase> oth)
{
  for (auto &cb : m_locationCallbackList)
    {
      if (!cb.IsNull ())
        {
          cb (oth->m_uid, oth->m_globalLocation);
        }
    }
}

void
RoboBase::Resurrection (uint8_t target)
{
  RoboJudge::Get ()->Resurrection (m_uid, target);
}

void
RoboBase::DoResurrection ()
{
  m_life = gRoboMaxLife[m_roboType][m_maxLifeLevel] * .2f;
  m_isDestroy = false;
  m_invincible = true;
  Timer invincibleTimer = Timer (Timer::CANCEL_ON_DESTROY);
  invincibleTimer.SetFunction (&RoboBase::CancelInvincible, this);
  invincibleTimer.SetDelay (Seconds (10));
  invincibleTimer.Schedule ();
}

void
RoboBase::CancelInvincible ()
{
  m_invincible = false;
}

void
RoboBase::InitPlot (FILE *fp)
{
  fprintf (fp, "ar:%u@%u@%s@%d@", m_uid, m_team, m_name.c_str (), gRoboMaxLife[m_roboType][0]);
  for (auto &p : m_boundaryPoint)
    {
      fprintf (fp, "%.3f:%.3f|", p.m_x, p.m_y);
    }
  fprintf (fp, "\n");
}

void
RoboBase::PlotSelf (FILE *fp)
{
  fprintf (fp, "rb:%u@%.3f:%.3f|%.3f@%.3f@%d@%d@%d@%d@%d\n", m_uid, m_globalLocation.m_x,
           m_globalLocation.m_y, m_globalRotation.m_phi,
           m_life / gRoboMaxLife[m_roboType][m_maxLifeLevel], m_level,
           m_weapon[AmmoType17].m_ammoNumber, m_weapon[AmmoType17].m_heat,
           m_weapon[AmmoType42].m_ammoNumber, m_weapon[AmmoType42].m_heat);
}

} // namespace ns3